package vg.modules.graph_settings;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;

import vg.services.graph_view_manager.interfaces.graph_comparison.AGraphComparisonView;
import vg.services.graph_view_manager.interfaces.graph_view.AGraphView;
import vg.services.graph_view_manager.interfaces.graph_view.IGraphRenderer;
import vg.services.graph_view_manager.interfaces.tab.ATab;
import vg.services.graph_view_manager.realization.graph_view.smart_graph_view.realization.actions.DefaultActionAdapter;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.AUIEvent;
import vg.services.plugin_manager.event_and_request.event.UIEventCloseTab;
import vg.services.plugin_manager.event_and_request.event.UIEventOpenNewTab;
import vg.services.plugin_manager.interfaces.IPlugin;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.additional_swing_components.FontChooserComboBox;
import vg.services.user_interface_manager.interfaces.UserInterfaceObserverAdapter;

public class GraphSettingsPlugin implements IPlugin{
    // Defines
    private final static String VERTEX_SPACING_LEFT = "GraphSettingsPlugin_vertex_spacing_left";
    private final static String VERTEX_SPACING_TOP = "GraphSettingsPlugin_vertex_spacing_top";
    private final static String VERTEX_SPACING_RIGHT = "GraphSettingsPlugin_vertex_spacing_right";
    private final static String VERTEX_SPACING_BOTTOM = "GraphSettingsPlugin_vertex_spacing_bottom";

    private final static String VERTEX_FONT_COLOR = "GraphSettingsPlugin_vertex_font_color";
    private final static String VERTEX_FILL_COLOR = "GraphSettingsPlugin_vertex_fill_color";
    private final static String VERTEX_SHAPE = "GraphSettingsPlugin_vertex_shape";
    private final static String GRAPH_ELEMENTS_FONT = "GraphSettingsPlugin_graph_elements_font";
    private final static String VERTEX_TEXT_ALIGN = "GraphSettingsPlugin_vertex_text_align";

	// Main data
	private boolean init = false;

	private int spacingLeft;
	private int spacingTop;
	private int spacingRight;
	private int spacingBottom;
	
	private String fontColor;
	private String fillColor;
	private String vertexShape;
	private String graphElementsFont;
	private String vertexTextAlign;
	
	private Map<Integer, AGraphView> tabs; // tab id -> graph view (or null)
	
	// Swing components
	private JComponent[] dialogComponents = null;
	private FontChooserComboBox fontChooser = null;
	private IntegerDocumentListener spacingLeftDoc = null;
	private IntegerDocumentListener spacingTopDoc = null;
	private IntegerDocumentListener spacingBottomDoc = null;
	private IntegerDocumentListener spacingRightDoc = null;
	private ColorPreview fontColorPreview = null;
	private ColorPreview vertexColorPreview = null;
	private JRadioButton leftAlignRadio = null;
	private JRadioButton centerAlignRadio = null;
	private JRadioButton rightAlignRadio = null;
	private JComboBox shapeList = null;

    // Mutex
    private final Object generalMutex = new Object();
	
	@Override
	public void install(PluginParameter parameter) throws Exception {
        tabs = new HashMap<Integer, AGraphView>();

        spacingBottom = 0;
        spacingTop = 0;
        spacingLeft = 0;
        spacingRight = 0;
        fontColor = "black";
        fillColor = "white";
        vertexShape = mxConstants.SHAPE_RECTANGLE;
        graphElementsFont = "Monospaced";
        vertexTextAlign = mxConstants.ALIGN_LEFT;

        loadSettings();

        // create components
		JMenuItem graphSettings = new JMenuItem("Graph settings");
		graphSettings.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				synchronized (generalMutex) {
                    if (!init)
                        init();

                    for (JComponent c: dialogComponents) {
                        SwingUtilities.updateComponentTreeUI(c);
                    }

                    if (JOptionPane.showConfirmDialog(null, dialogComponents, "Graph visualization settings", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) == 0) {
                        String fontName = fontChooser.getSelectedFontName();
                        if (fontName != null)
                            graphElementsFont = fontName;
                        spacingLeft = spacingLeftDoc.getValue();
                        spacingTop = spacingTopDoc.getValue();
                        spacingRight = spacingRightDoc.getValue();
                        spacingBottom = spacingBottomDoc.getValue();
                        fontColor = mxUtils.hexString(fontColorPreview.getBackground());
                        fillColor = mxUtils.hexString(vertexColorPreview.getBackground());
                        if (leftAlignRadio.isSelected()) {
                            vertexTextAlign = mxConstants.ALIGN_LEFT;
                        } else if (centerAlignRadio.isSelected()) {
                            vertexTextAlign = mxConstants.ALIGN_CENTER;
                        } else if (rightAlignRadio.isSelected()) {
                            vertexTextAlign = mxConstants.ALIGN_RIGHT;
                        }
                        vertexShape = shapeList.getSelectedItem().toString();

                        // save settings
                        saveSettings();

                        // apply settings to exist graph views
                        for (ATab bufTab : tabs.values()) {
                            if (bufTab.getTabType() == ATab.DEF_GRAPH_VIEW_TAB_TYPE) {
                                AGraphView graphView = (AGraphView)bufTab;
                                graphView.addAction(new DefaultActionAdapter("set stylesheet action") {
                                    @Override
                                    public void action(final IGraphRenderer graphRenderer) {
                                        graphRenderer.setStylesheet(generateStyleSheet());
                                    }
                                });
                            }
                        }
                    }
                }
			}
		});
		
		parameter.userInterfaceManager.addObserver(new UserInterfaceObserverAdapter() {
            @Override
            public void dispatchEvent(AUIEvent event) {
                switch(event.getType()) {
                    case DEF_OPEN_NEW_TAB: {
                        UIEventOpenNewTab bufEvent = (UIEventOpenNewTab)event;
                        ATab tab = bufEvent.getTab();
                        if (tab == null)
                            return;

                        switch (tab.getTabType()) {
                            case ATab.DEF_GRAPH_VIEW_TAB_TYPE: {
                                synchronized (generalMutex) {
                                    AGraphView graphView = (AGraphView)tab;
                                    tabs.put(bufEvent.getTabId(), graphView);
                                    graphView.addAction(new DefaultActionAdapter("set stylesheet action") {
                                        @Override
                                        public void action(final IGraphRenderer graphRenderer) {
                                            graphRenderer.setStylesheet(generateStyleSheet());
                                        }
                                    });
                                }
                                break;
                            }

                            case ATab.DEF_GRAPH_COMPARISON_TAB_TYPE: {
                                synchronized (generalMutex) {
                                    AGraphComparisonView graphComparisonView = (AGraphComparisonView)tab;
                                    AGraphView graphView = graphComparisonView.getMainGraphView();
                                    if (graphView == null)
                                        return;

                                    tabs.put(bufEvent.getTabId(), graphView);
                                    graphView.addAction(new DefaultActionAdapter("set stylesheet action") {
                                        @Override
                                        public void action(final IGraphRenderer graphRenderer) {
                                            graphRenderer.setStylesheet(generateStyleSheet());
                                        }
                                    });
                                }
                                break;
                            }

                            default:
                                break;
                        }
                        break;
                    }

                    case DEF_CLOSE_TAB: {
                        UIEventCloseTab bufEvent = (UIEventCloseTab)event;
                        synchronized (generalMutex) {
                            tabs.remove(bufEvent.getTabId());
                        }
                        break;
                    }

                    default:
                        break;
                }
            }
        });

        parameter.userInterfaceManager.addMenuItem(graphSettings, "edit");
	}
	
//=============================================================================
//-----------------PRIVATE METHODS---------------------------------------------
	private void loadSettings() {
        spacingLeft = MainManager.config.getIntegerProperty(VERTEX_SPACING_LEFT);
        spacingTop = MainManager.config.getIntegerProperty(VERTEX_SPACING_TOP);
        spacingRight = MainManager.config.getIntegerProperty(VERTEX_SPACING_RIGHT);
        spacingBottom = MainManager.config.getIntegerProperty(VERTEX_SPACING_BOTTOM);

        fontColor = MainManager.config.getStringProperty(VERTEX_FONT_COLOR);
        fillColor = MainManager.config.getStringProperty(VERTEX_FILL_COLOR);
        vertexShape = MainManager.config.getStringProperty(VERTEX_SHAPE);
        vertexTextAlign = MainManager.config.getStringProperty(VERTEX_TEXT_ALIGN);
        graphElementsFont = MainManager.config.getStringProperty(GRAPH_ELEMENTS_FONT);
	}

    private void saveSettings() {
        MainManager.config.setProperty(GRAPH_ELEMENTS_FONT, graphElementsFont);
        MainManager.config.setProperty(VERTEX_FILL_COLOR, fillColor);
        MainManager.config.setProperty(VERTEX_FONT_COLOR, fontColor);
        MainManager.config.setProperty(VERTEX_SHAPE, vertexShape);
        MainManager.config.setProperty(VERTEX_TEXT_ALIGN, vertexTextAlign);
        MainManager.config.setProperty(VERTEX_SPACING_BOTTOM, String.valueOf(spacingBottom));
        MainManager.config.setProperty(VERTEX_SPACING_TOP, String.valueOf(spacingTop));
        MainManager.config.setProperty(VERTEX_SPACING_LEFT, String.valueOf(spacingLeft));
        MainManager.config.setProperty(VERTEX_SPACING_RIGHT, String.valueOf(spacingRight));
    }
	
	private Map<String, Object> generateStyleSheet() {
		Map<String, Object> style = new HashMap<String, Object>();
		
		style.put(mxConstants.STYLE_SHAPE, vertexShape);
		style.put(mxConstants.STYLE_FONTCOLOR, fontColor);
		style.put(mxConstants.STYLE_FILLCOLOR, fillColor);
		style.put(mxConstants.STYLE_SPACING_LEFT, spacingLeft);
		style.put(mxConstants.STYLE_SPACING_RIGHT, spacingRight);
		style.put(mxConstants.STYLE_SPACING_TOP, spacingTop);
		style.put(mxConstants.STYLE_SPACING_BOTTOM, spacingBottom);
		style.put(mxConstants.STYLE_ALIGN, vertexTextAlign);
		style.put(mxConstants.STYLE_FONTFAMILY, graphElementsFont);
		
		style.put(mxConstants.STYLE_FONTFAMILY, graphElementsFont);
		style.put(mxConstants.STYLE_ENDARROW, mxConstants.ARROW_CLASSIC);
		style.put(mxConstants.STYLE_ROUNDED, "1");
		
		return style;
	}
	
	private void init() {
		if (init)
			return;
		
		loadSettings();
		
		fontChooser = new FontChooserComboBox(MainManager.threadPool, graphElementsFont);
		
		fontColorPreview = new ColorPreview();
		fontColorPreview.setBackground(mxUtils.parseColor(fontColor));
		
		JButton fontColorButton = new JButton("change");
		fontColorButton.addActionListener(new ActionListener() {
			@Override
            public void actionPerformed(ActionEvent arg0) {
                try {
                    Color newColor = JColorChooser.showDialog(MainManager.view.getMainFrame(), "Set font color", fontColorPreview.getBackground());
                    if (newColor != null) {
                        fontColorPreview.setBackground(newColor);
                    }
                } catch (Throwable ex) {
                    MainManager.windowMessage.errorMessage("Please change window style. This function doesn't work with current style.", "Window style error");
                    MainManager.logger.printException(ex);
                }
			}
		});

		JPanel colorFontPane = new JPanel();
		colorFontPane.setLayout(new BoxLayout(colorFontPane, BoxLayout.X_AXIS));
		colorFontPane.add(new JLabel("Font color"));
		colorFontPane.add(Box.createHorizontalGlue());
		colorFontPane.add(fontColorPreview);
		colorFontPane.add(Box.createRigidArea(new Dimension(5,0)));
		colorFontPane.add(fontColorButton);
		
		JPanel vertexSpacing = new JPanel();
		vertexSpacing.setBorder(BorderFactory.createTitledBorder("Vertex spacing"));
		vertexSpacing.setLayout(new GridBagLayout());
		
		GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0);
		vertexSpacing.add(new JLabel("Left"), gbc);
		
		gbc = new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
		JTextField tf = new JTextField();
		tf.setText(String.valueOf(spacingLeft));
		this.spacingLeftDoc =  new IntegerDocumentListener(tf, spacingLeft);
		tf.getDocument().addDocumentListener(this.spacingLeftDoc);
		vertexSpacing.add(tf, gbc);
		
		gbc = new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0);
		vertexSpacing.add(new JLabel("Top"), gbc);
		
		gbc = new GridBagConstraints(1, 1, 1, 1, 1, 0, GridBagConstraints.NORTH,
				GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
		tf = new JFormattedTextField();
		tf.setText(String.valueOf(spacingTop));
		this.spacingTopDoc =  new IntegerDocumentListener(tf, spacingTop);
		tf.getDocument().addDocumentListener(this.spacingTopDoc);
		vertexSpacing.add(tf, gbc);
		
		gbc = new GridBagConstraints(0, 2, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0);
		vertexSpacing.add(new JLabel("Right"), gbc);
		
		gbc = new GridBagConstraints(1, 2, 1, 1, 1, 0, GridBagConstraints.NORTH,
				GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
		tf = new JFormattedTextField();
		tf.setText(String.valueOf(spacingRight));
		this.spacingRightDoc =  new IntegerDocumentListener(tf, spacingRight);
		tf.getDocument().addDocumentListener(this.spacingRightDoc);
		vertexSpacing.add(tf, gbc);
		
		gbc = new GridBagConstraints(0, 3, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0);
		vertexSpacing.add(new JLabel("Bottom"), gbc);
		
		gbc = new GridBagConstraints(1, 3, 1, 1, 1, 0, GridBagConstraints.NORTH,
				GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
		tf = new JFormattedTextField();
		tf.setText(String.valueOf(spacingBottom));
		this.spacingBottomDoc =  new IntegerDocumentListener(tf, spacingBottom);
		tf.getDocument().addDocumentListener(this.spacingBottomDoc);
		vertexSpacing.add(tf, gbc);
		
		vertexColorPreview = new ColorPreview();
		vertexColorPreview.setBackground(mxUtils.parseColor(fillColor));
		
		JButton vertexColorButton = new JButton("change");
		vertexColorButton.addActionListener(new ActionListener() {
			@Override
            public void actionPerformed(ActionEvent arg0) {
                try {
                    Color newColor = JColorChooser.showDialog(MainManager.view.getMainFrame(), "Set font color", vertexColorPreview.getBackground());
                    if (newColor != null) {
                        vertexColorPreview.setBackground(newColor);
                    }
                } catch (Throwable ex) {
                    MainManager.windowMessage.errorMessage("Please change window style. This function doesn't work with current style.", "Window style error");
                    MainManager.logger.printException(ex);
                }
            }
		});

		JPanel vertexColorPane = new JPanel();
		vertexColorPane.setLayout(new BoxLayout(vertexColorPane, BoxLayout.X_AXIS));
		vertexColorPane.add(new JLabel("Default vertex color"));
		vertexColorPane.add(Box.createHorizontalGlue());
		vertexColorPane.add(this.vertexColorPreview);
		vertexColorPane.add(Box.createRigidArea(new Dimension(5,0)));
		vertexColorPane.add(vertexColorButton);
		
		JPanel textAlignPane = new JPanel();
		textAlignPane.setLayout(new BoxLayout(textAlignPane, BoxLayout.X_AXIS));
		textAlignPane.setBorder(BorderFactory.createTitledBorder("Vertex text align"));
		this.leftAlignRadio = new JRadioButton("left",
				vertexTextAlign.equals(mxConstants.ALIGN_LEFT));
		this.centerAlignRadio = new JRadioButton("center",
				vertexTextAlign.equals(mxConstants.ALIGN_CENTER));
		this.rightAlignRadio = new JRadioButton("right",
				vertexTextAlign.equals(mxConstants.ALIGN_RIGHT));
		textAlignPane.add(this.leftAlignRadio);
		textAlignPane.add(Box.createHorizontalGlue());
		textAlignPane.add(this.centerAlignRadio);
		textAlignPane.add(Box.createHorizontalGlue());
		textAlignPane.add(this.rightAlignRadio);
		
		ButtonGroup group = new ButtonGroup();
		group.add(leftAlignRadio);
		group.add(centerAlignRadio);
		group.add(rightAlignRadio);
		
		shapeList = new JComboBox(new Object[] {
				mxConstants.SHAPE_RECTANGLE,
				mxConstants.SHAPE_ELLIPSE,
				mxConstants.SHAPE_DOUBLE_ELLIPSE,
				mxConstants.SHAPE_RHOMBUS,
				mxConstants.SHAPE_CYLINDER,
				mxConstants.SHAPE_TRIANGLE,
				mxConstants.SHAPE_HEXAGON,
				mxConstants.SHAPE_CLOUD
		});
		
		dialogComponents = new JComponent[] { 
				new JLabel("Font"), 
				fontChooser, 
				colorFontPane,
				vertexSpacing, 
				vertexColorPane,
				textAlignPane,
				new JLabel("Default vertex shape"),
				shapeList
		};

		init = true;
	}

	private static class IntegerDocumentListener implements DocumentListener {
		private JTextField tf;
		private int value;

		public IntegerDocumentListener(JTextField textField, int oldvalue) {
			tf = textField;
			value = oldvalue;
		}

		public void removeUpdate(DocumentEvent e) {
			String str = tf.getText();
			if (str != null && str.length() > 0) {
				try {
					value = Integer.parseInt(str);
				} catch (NumberFormatException e1) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							tf.setText(String.valueOf(value));
							tf.repaint();
						}
					});
				}
			} else {
				value = 0;
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						tf.setText(String.valueOf(value));
						tf.repaint();
					}
				});
			}


		}
		public void insertUpdate(DocumentEvent e) {
			removeUpdate(e);
		}
		public void changedUpdate(DocumentEvent e) {

		}

		public int getValue() {
			return value;
		}
	}
	
	private static class ColorPreview extends JComponent {
		private static final long serialVersionUID = -2276526078891878352L;
		ColorPreview() {
			setMinimumSize(new Dimension(24,24));
			setPreferredSize(getMinimumSize());
			setMaximumSize(getPreferredSize());
		}

		@Override
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			g.setColor(new Color(229, 160, 63));
			Rectangle bg = new Rectangle(0, 0, getWidth(), getHeight());
			bg = bg.intersection(g.getClipBounds());
			g.fillRect(bg.x, bg.y, bg.width, bg.height);
			
			g.setColor(getBackground());
			bg = new Rectangle(1, 1, getWidth() - 2, getHeight() - 2);
			bg = bg.intersection(g.getClipBounds());
			g.fillRect(bg.x, bg.y, bg.width, bg.height);
		}
	}
}

